﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using Snowdreamist.ComponentModel.Plugin;
using Snowdreamist.ComponentModel.Primitives;

namespace Snowdreamist.ComponentModel.Services
{
    public abstract class ImportedPluginManagerBase : IImportedPluginManager
    {
        public ImportedPluginManagerBase()
        {
            // add all tracer
            this._tracers[(int)PluginShareMode.Instance] = new InstanceImportedPluginTracer();
        }

        public virtual void Initialize() { }

        public ImportedPlugin<T> GetPlugin<T>(object instance, ContractMetadata metadata)
        {
            if (metadata == null)
                throw new ArgumentNullException("metadata");
            return this.GetPluginInstance<T, IDefaultMetadata>(instance, null, metadata).FirstOrDefault();
        }

        public ImportedPlugin<T, TMetadata> GetPlugin<T, TMetadata>(object instance, ContractMetadata metadata)
        {
            if (metadata == null)
                throw new ArgumentNullException("metadata");
            return this.GetPluginInstance<T, TMetadata>(instance, null, metadata).FirstOrDefault();
        }

        public IEnumerable<ImportedPlugin<T>> GetPlugins<T>(object instance, ContractMetadata metadata)
        {
            if (metadata == null)
                throw new ArgumentNullException("metadata");
            return this.GetPluginInstance<T, IDefaultMetadata>(instance, null, metadata);
        }

        public IEnumerable<ImportedPlugin<T, TMetadata>> GetPlugins<T, TMetadata>(object instance, ContractMetadata metadata)
        {
            if (metadata == null)
                throw new ArgumentNullException("metadata");
            return this.GetPluginInstance<T, TMetadata>(instance, null, metadata);
        }

        public ImportedPlugin<T> GetPlugin<T>(Type ownerType, ContractMetadata metadata)
        {
            if (metadata == null)
                throw new ArgumentNullException("metadata");
            return this.GetPluginInstance<T, IDefaultMetadata>(null, ownerType, metadata).FirstOrDefault();
        }

        public ImportedPlugin<T, TMetadata> GetPlugin<T, TMetadata>(Type ownerType, ContractMetadata metadata)
        {
            if (metadata == null)
                throw new ArgumentNullException("metadata");
            return this.GetPluginInstance<T, TMetadata>(null, ownerType, metadata).FirstOrDefault();
        }

        public IEnumerable<ImportedPlugin<T>> GetPlugins<T>(Type ownerType, ContractMetadata metadata)
        {
            if (metadata == null)
                throw new ArgumentNullException("metadata");
            return this.GetPluginInstance<T, IDefaultMetadata>(null, ownerType, metadata);
        }

        public IEnumerable<ImportedPlugin<T, TMetadata>> GetPlugins<T, TMetadata>(Type ownerType, ContractMetadata metadata)
        {
            if (metadata == null)
                throw new ArgumentNullException("metadata");
            return this.GetPluginInstance<T, TMetadata>(null, ownerType, metadata);
        }

        public event EventHandler<UndeterminedPluginsEventArgs> UndetermindImportedPlugin;

        public virtual void OnRuntimeDisposing() { }

        #region protected

        /// <summary>
        /// Select plugins when cannot make decision
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <typeparam name="TMetadata"></typeparam>
        /// <param name="plugins"></param>
        /// <param name="metadata"></param>
        /// <returns></returns>
        protected virtual IEnumerable<ImportedPlugin<T, TMetadata>> SelectImportedPlugin<T, TMetadata>(
            IEnumerable<ImportedPlugin<T, TMetadata>> plugins,
            ContractMetadata metadata
            )
        {
            return plugins;
        }

        protected virtual IEnumerable<ImportedPlugin<T, TMetadata>> GetPluginInstance<T, TMetadata>(
            object instance,
            Type ownerType,
            ContractMetadata metadata
            )
        {
            // try get from tracer
            var tracer = this.GetPluginTracer(metadata);
            var plugins = tracer.GetPlugins<T, TMetadata>(instance, ownerType, metadata);
            if (plugins == null || plugins.Count() == 0)
            {
                // try to import
                plugins = this.ResolvePlugins<T, TMetadata>(metadata);
                if (plugins == null || plugins.Count() == 0)
                {
                    return Enumerable.Empty<ImportedPlugin<T, TMetadata>>();
                }
                else
                {
                    tracer.Trace(instance, ownerType, metadata, plugins);
                    return plugins;
                }
            }
            else
            {
                return plugins;
            }
        }

        protected virtual IEnumerable<ImportedPlugin<T, TMetadata>> ResolvePlugins<T, TMetadata>(ContractMetadata metadata)
        {
            var plugins = this.GetPlugins<T, TMetadata>(metadata);

            // check is plugins satisfy metadata restriction
            if ((plugins == null || plugins.Count() == 0) && metadata.CandidateMode == CandidateMode.One)
                throw new NotFoundException("Cannot find plugin satisfy contract metadata");

            if (plugins.Count() > 1 && metadata.CandidateMode == CandidateMode.One || metadata.CandidateMode == CandidateMode.ZeroOrOne)
            {
                // need to select one
                plugins = this.SelectImportedPlugin<T, TMetadata>(plugins, metadata);
                // check again
                if (plugins.Count() > 1)
                    throw new MultipleValuesFoundException("Cannot decide which plugin to import");
            }

            // satisfy
            return plugins;
        }

        protected abstract IEnumerable<ImportedPlugin<T, TMetadata>> GetPlugins<T, TMetadata>(ContractMetadata metadata);

        #endregion protected

        #region private

        private readonly Dictionary<int, ImportedPluginTracer> _tracers = new Dictionary<int, ImportedPluginTracer>();

        private ImportedPluginTracer GetPluginTracer(ContractMetadata metadata)
        {
            return this._tracers[(int)metadata.ShareMode];
        }

        #endregion private
    }
}